home *** CD-ROM | disk | FTP | other *** search
- //
- // MiscMergeTemplate.m -- a data container and parser for merge templates
- // Written by Don Yacktman Copyright (c) 1995 by Don Yacktman.
- // Version 1.0. All rights reserved.
- // This notice may not be removed from this source code.
- //
- // This object is included in the MiscKit by permission from the author
- // and its use is governed by the MiscKit license, found in the file
- // "LICENSE.rtf" in the MiscKit distribution. Please refer to that file
- // for a list of all applicable permissions and restrictions.
- //
-
-
- #import <misckit/misckit.h>
- #import <misckit/miscmerge.h>
- #import <objc/objc-runtime.h>
-
- #import "_MiscMergeCopyCommand.h"
- #import "_MiscMergeFieldCommand.h"
- #import "_MiscMergeDelayedParseCommand.h"
-
- @implementation MiscMergeTemplate
- /*" This class conatins the template that is used by a merge engine.
- It performs two functions: (1) parse a string or text file into the
- commands required by a merge ending and (2) act as a container for
- the commands once they have been parsed, providing them to a merge
- engine as needed.
-
- Typically, MiscMergeTemplate objects are used in a very simple way: they
- are instantiated, given the ASCII text or string to parse, and then passed
- to MiscMergeEngine instances as needed. That's it!
-
- It should be noted that template text which is simply copied from the
- template into the merged output (ie. an text outside of a merge command)
- is actually turned into a special “copy” command by the parsing algorithm.
- This allows the merge engine to deal exclusively with MiscMergeCommand
- subclasses to perform a merge. This implementation detail should not
- affect anything that would normally be done with this object, but it is
- important to understand this fact if attempting to understand the data
- structure created by the parsing routines.
- "*/
-
- // Note: delimiters may be escaped with a "\".
- static char startChar = '«';
- static char endChar = '»';
-
- + (char)startFieldDelimiter
- /*" Returns the character used to start a merge command.
- "*/
- {
- return startChar;
- }
-
- + (char)endFieldDelimiter
- /*" Returns the character used to end a merge command.
- "*/
- {
- return endChar;
- }
-
- + setFieldDelimitersStart:(char)aChar1 end:(char)aChar2
- /*" Sets the characters used to start and end a merge command. Returns
- self. It is highly recommended that %{aChar1} and %{aChar2} be different
- characters.
- "*/
- {
- startChar = aChar1;
- endChar = aChar2;
- return self;
- }
-
- + getClassForCommand:(MiscString *)aCommand
- /*" Given the command string %{aCommand}, this method determines which
- MiscMergeCommand subclass implements the merge command. It returns the
- class object needed to create instances of the MiscMergeCommand subclass.
-
- This method works by asking the runtime if it can find Objective-C classes
- with specific names. The name that is looked up is build from the first
- word found in %{aCommand}. The first word is turnd to all lower case, with
- the first letter upper case, and then sandwiched between “Merge” and
- “Command”. For example, the merge command “«If xxx = y»” has the word “If”
- as the first word. Thus, the class “MergeIfCommand” will be searched for.
- If the desired class cannot be found, then it is assumed that the merge
- command is giving the name of a field wich should be inserted into the
- output document.
-
- To avoid name space conflicts, all internal merge commands actually use
- a slightly different name. Thus, there really is no “MergeIfCommand“ to
- be found. This method, when it doesn't find the “MergeIfCommand” class, will
- search for another class, with a private name. That class will be found.
- (If it weren't found, then the default “field” command class would be
- returned.) This allows a programmer to override any built in command.
- To override the “if” command, simply create a “MergeIfCommand“ class
- and it will be found before the built in class. If a programmer wishes
- to make a particular command, such as “omit”, inoperative, this technique
- may be used to override with a MiscMergeCommand subclass that does nothing.'
-
- Note that if the command string %{aCommand} contains merge commands inside
- itself, then a special “delayed command” class will be returned. That
- class will, during a merge, create an engine, perform a merge on its
- text, and then parse itself into the correct type of command. This allows
- merges to contain commands that change depending upon the data records.
- "*/
- {
- // Look for "MergeXxxCommand" class for user overrides.
- // Failing that, look for "_MiscMergeXxxCommand" for built-in commands.
- // Failing that, we assume we are dealing with a field.
- id theClassName = [MiscString new];
- id foundClass = nil;
- id individualCommand = [aCommand wordNum:0];
-
- if ([aCommand numOfChar:startChar]) {
- // may have a nested merge to deal with, if so
- // we use the "delayed merge" class.
- int i; BOOL flag = NO;
- for (i=0; i<[aCommand numOfChar:startChar]; i++) {
- // see if all delimiters are escaped or not
- if ([aCommand charAt:
- ([aCommand spotOf:startChar occurrenceNum:i
- caseSensitive:NO] - 1)] != '\\')
- flag = YES;
- }
- // found an unescaped delimiter, so delay the parsing until
- // we are actually doing a merge.
- if (flag) return [_MiscMergeDelayedParseCommand class];
- }
-
- [individualCommand trimWhiteSpaces];
- [individualCommand toLower];
- [individualCommand capitalizeEachWord];
- theClassName = [[MiscString alloc] initFromFormat:"Merge%sCommand",
- [individualCommand stringValue]];
- foundClass = objc_lookUpClass([theClassName stringValue]);
- if (!foundClass) {
- theClassName = [[MiscString alloc]
- initFromFormat:"_MiscMerge%sCommand",
- [individualCommand stringValue]];
- foundClass = objc_lookUpClass([theClassName stringValue]);
- }
- [theClassName free];
- [individualCommand free];
- return (foundClass ? foundClass : [_MiscMergeFieldCommand class]);
- }
-
- - init
- /*" Initializes the MiscMergeTemplate instance and returns self. This is the
- designated initializer.
- "*/
- {
- [super init];
- commands = [[List alloc] init];
- return self;
- }
-
- - initWithString:(MiscString *)aString
- /*" Initializes the MiscMergeTemplate instance, parses the template from
- %{aString}, and returns self. This method is provided for convenience.
- "*/
- {
- [self init];
- [self parseFromString:aString];
- return self;
- }
-
- - initFromFile:(MiscFile *)aFile
- /*" Initializes the MiscMergeTemplate instance, parses the template from
- the file pointed to by %{aFile}, and returns self. This method is provided
- for convenience.
- "*/
- {
- [self init];
- [self parseFromFile:aFile];
- return self;
- }
-
- - initFromFileNamed:(MiscString *)aFileName
- /*" Initializes the MiscMergeTemplate instance, parses the template from
- the file named %{aFileName}, and returns self. This method is provided
- for convenience.
- "*/
- {
- [self init];
- [self parseFromFileNamed:aFileName];
- return self;
- }
-
- - parseFromString:(MiscString *)aString
- /*" Parses the MiscMergeTemplate from %{aString}. Returns self.
- "*/
- {
- id tempString = [aString copy];
- int count;
-
- [commands freeObjects];
- while ([tempString length] > 0) {
- MiscString *theText = nil;
- MiscString *theCommand = nil;
- MiscMergeCommand *textCommand, *mergeCommand;
-
- // cut block of text from the string, remove first delimiter
- // theText = [tempString extractPart:0 useAsDelimiter:startChar];
- // can't use extractPart since we want to allow escaped
- // delimiters in the document.
- count = 0;
- while (!theText) {
- int right = [tempString spotOf:startChar
- occurrenceNum:count caseSensitive:NO] - 1;
- if (right < -1) {
- theText = [tempString copy];
- } else if (right < 0) {
- theText = [MiscString new];
- } else {
- if ([tempString charAt:right] == '\\') {
- count += 1;
- } else {
- theText = [tempString midFrom:0 to:right];
- }
- }
- }
- [tempString replace:[theText stringValue] with:""];
- [tempString replaceFrom:0 length:1 with:""];
-
- // cut command text from the string, remove closing delimiter
-
- // **** won't allow nested commands since we don't check to
- // see if there are any start delimiters before we get to the
- // end delimiter. If there are, then we need to count that
- // many end delimiters to get the nested commands... *****
- // This needs to be fixed for the production version!!!
-
- // theCommand = [tempString extractPart:0 useAsDelimiter:endChar];
- count = 0;
- { // scan for end delimiter, allowing for nesting
- int pos, nesting = 0; int right = -2;
- for (pos = 0; pos < [tempString length]; pos++) {
- if ([tempString charAt:pos] == endChar) {
- if ((pos < 1) || ([tempString charAt:(pos - 1)] != '\\')) {
- if (nesting) nesting--;
- else if (right < -1) {
- right = pos - 1;
- }
- }
- } else if ([tempString charAt:pos] == startChar) {
- if ((pos < 1) || ([tempString charAt:(pos - 1)] != '\\')) {
- nesting++;
- }
- }
- }
- if (right < -1) theCommand = [tempString copy];
- else if (right < 1) theCommand = [tempString new];
- else theCommand = [tempString midFrom:0 to:right];
- }
- [tempString replace:[theCommand stringValue] with:""];
- [tempString replaceFrom:0 length:1 with:""];
-
- // parse into command objects
- textCommand = [[_MiscMergeCopyCommand alloc] initFrom:theText];
- mergeCommand = [[[[self class] getClassForCommand:theCommand]
- alloc] initFrom:theCommand];
-
- // add to our list of commands
- [commands addObject:textCommand];
-
- // (Note: last time through the loop, this is likely to be a
- // bogus command and really shouldn't be added! However, it
- // may be OK, if the template actually ends with a command.
- // As such, the only place this affects has a special case
- // in it (see MME+Symbols.m where fields are resolved when
- // leaveDelimiters is on). We probably won't fix this bug
- // since its effects are 100% benign and it is easier than
- // writing a "real" parser for the templates. :-)
- [commands addObject:mergeCommand]; // need to turn into cmd obj 1st
-
- [theText free];
- [theCommand free];
- }
- return self;
- }
-
- - parseFromFile:(MiscFile *)aFile
- /*" Parses the MiscMergeTemplate from the file pointed to by %{aFileName}.
- Returns self.
- "*/
- {
- MiscString *fileString = [MiscString new];
- [fileString loadFromFile:[aFile fullPath]];
- [self parseFromString:fileString];
- return self;
- }
-
- - parseFromFileNamed:(MiscString *)aFileName
- /*" Parses the MiscMergeTemplate from the file named %{aFileName}.
- Returns self.
- "*/
- {
- MiscString *fileString = [MiscString new];
- [fileString loadFromFile:[aFileName stringValue]];
- [self parseFromString:fileString];
- [fileString free];
- return self;
- }
-
- - commands
- /*" Returns a List object containing, in order, all the merge commands
- to be executed by the merge engine.
- "*/
- {
- return commands;
- }
-
- - commandAt:(unsigned)index
- /*" Returns the ith merge command to be executed by the merge engine.
- "*/
- {
- return [commands objectAt:index];
- }
-
- @end
-